/*
   \file  image.cc
   \brief Encapsulation of images as required by libgist driver program.

   This file defines the non-inline member functions of the
   gistdrv::image class. See its header file for more info.
*/

/*
   This file is part of libgist.

   libgist is free software; you can redistribute it and/or modify it
   under the terms of the GNU General Public License as published by the
   Free Software Foundation; either version 2 of the License, or (at your
   option) any later version.

   libgist is distributed in the hope that it will be useful, but WITHOUT
   ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
   FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
   for more details.

   You should have received a copy of the GNU General Public License
   along with libgist; if not, write to the Free Software Foundation,
   Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
*/

/*
   REVISION HISTORY

   $HeadURL: http://libgist.googlecode.com/svn/branches/wu_dev/drv/image.cc $
   $Id: image.cc 55 2011-09-02 18:18:00Z libgist@gmail.com $
*/

//------------------------------ HEADERS --------------------------------

// gistdrv
#include "image.hh"

// libdevil
#include <IL/ilu.h>

// Boost.GIL
#include <boost/gil/image_view_factory.hpp>
#include <boost/gil/image_view.hpp>

// Boost.Thread
#include <boost/thread/mutex.hpp>

//----------------------------- NAMESPACE -------------------------------

namespace gistdrv {

//------------------------------ GLOBALS --------------------------------

namespace {

enum error_codes {
   LOAD_ERROR = 21,
   SAVE_ERROR,
} ;

// Helper global object to initialize libdevil
struct libdevil_initializer {
   libdevil_initializer() {ilInit() ; iluInit() ;}
} ;
libdevil_initializer g_libdevil_initializer ;

/// Unfortunately, libdevil is not thread-safe. Since libgist and its
/// driver program are multithreaded, we have to synchronize libdevil
/// calls ourselves.
boost::mutex g_mutex ;

// Quick helper to automatically lock/unlock above mutex
struct lock_libdevil {
    lock_libdevil() {g_mutex.lock()   ;}
   ~lock_libdevil() {g_mutex.unlock() ;}
} ;

} // end of local anonymous namespace encapsulating above helper

//-------------------------- INITIALIZATION -----------------------------

image::image(const std::string& file_name)
{
   lock_libdevil lock ;
   ilGenImages(1, &m_image_id) ;
   ilBindImage(m_image_id) ;
   ilLoadImage(file_name.c_str()) ;
   if (ilGetError() != IL_NO_ERROR)
      throw load_error(file_name) ;
}

//------------------------- IMAGE OPERATIONS ----------------------------

image& image::resize(int width, int height)
{
   lock_libdevil lock ;
   ilBindImage(m_image_id) ;
   iluImageParameter(ILU_FILTER, ILU_BILINEAR) ;
   iluScale(width, height, 1) ;
   return *this ;
}

image& image::grayscale()
{
   lock_libdevil lock ;
   ilBindImage(m_image_id) ;
   ilConvertImage(IL_LUMINANCE, IL_UNSIGNED_BYTE) ;
   return *this ;
}

//------------------------- IMAGE CONVERSION ----------------------------

image::operator boost::gil::gray8_image_t() const
{
   lock_libdevil lock ;
   ilBindImage(m_image_id) ;
   ILuint clone_id = ilCloneCurImage() ;
   ilBindImage(clone_id) ;
   if (ilGetInteger(IL_IMAGE_FORMAT) != IL_LUMINANCE)
      ilConvertImage(IL_LUMINANCE, IL_UNSIGNED_BYTE) ;

   ILint width   = ilGetInteger(IL_IMAGE_WIDTH) ;
   ILint height  = ilGetInteger(IL_IMAGE_HEIGHT) ;
   ILubyte* data = ilGetData() ;

   namespace gil = boost::gil ;
   gil::gray8c_view_t src(gil::interleaved_view(width, height,
      reinterpret_cast<const gil::gray8_pixel_t*>(data), width)) ;
   gil::gray8_image_t img(width, height) ;
   gil::copy_pixels(src, gil::view(img)) ;

   ilBindImage(m_image_id) ;
   ilDeleteImages(1, &clone_id) ;
   return img ;
}

image::operator boost::gil::rgb8_image_t() const
{
   lock_libdevil lock ;
   ilBindImage(m_image_id) ;
   ILuint clone_id = ilCloneCurImage() ;
   ilBindImage(clone_id) ;
   if (ilGetInteger(IL_IMAGE_FORMAT) != IL_RGB)
      ilConvertImage(IL_RGB, IL_UNSIGNED_BYTE) ;

   ILint width   = ilGetInteger(IL_IMAGE_WIDTH) ;
   ILint height  = ilGetInteger(IL_IMAGE_HEIGHT) ;
   ILubyte* data = ilGetData() ;

   namespace gil = boost::gil ;
   gil::rgb8c_view_t src(gil::interleaved_view(width, height,
      reinterpret_cast<const gil::rgb8_pixel_t*>(data), width*3)) ;
   gil::rgb8_image_t img(width, height) ;
   gil::copy_pixels(src, gil::view(img)) ;

   ilBindImage(m_image_id) ;
   ilDeleteImages(1, &clone_id) ;
   return img ;
}

//----------------------------- IMAGE I/O -------------------------------

void image::save(const std::string& file_name) const
{
   lock_libdevil lock ;
   ilBindImage(m_image_id) ;
   ilEnable(IL_FILE_OVERWRITE) ;
   ilSaveImage(file_name.c_str()) ;
   if (ilGetError() != IL_NO_ERROR)
      throw save_error(file_name) ;
}

//----------------------------- CLEAN-UP --------------------------------

image::~image()
{
   lock_libdevil lock ;
   ilDeleteImages(1, &m_image_id) ;
}

//---------------------------- EXCEPTIONS -------------------------------

image::load_error::load_error(const std::string& file_name)
   : exception(LOAD_ERROR, file_name + ": unsupported image type")
{}

image::save_error::save_error(const std::string& file_name)
   : exception(SAVE_ERROR, file_name + ": unable to save")
{}

//-----------------------------------------------------------------------

} // end of namespace encapsulating this file's definitions

/* So things look consistent in everyone's emacs... */
/* Local Variables: */
/* indent-tabs-mode: nil */
/* End: */
